﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;

namespace Skugo.Shared
{
    public delegate void RpcSenderFn(Byte[] data);

    /// <summary>
    /// A target is a root object and root property node paired together.
    /// </summary>
    public struct RpcTarget
    {
        public Object TargetObject;
        public PropertyNode[] FlattenedHierarchy;
    }

    /// <summary>
    /// The type of message we send or receieve.
    /// </summary>
    private enum MessageType : byte
    {
        /// <summary>
        /// A property value is being set.
        /// </summary>
        Set,

        /// <summary>
        /// A method call is being made (and all parameter values are provided).
        /// </summary>
        Call,

        /// <summary>
        /// The property tree has changed, and therefore a full tree update is
        /// required (but note it could be multiple trees due to muli-selection).
        /// </summary>
        Trees,

        /// <summary>
        /// Typically when the tree is first built, we need a snapshot of all the properties in that tree.
        /// This message could be combined with the trees update, but it may come in useful later if we
        /// find that sending a full update is actually less expensive than deltas (when deltas are large).
        /// </summary>
        PropertySnapshot,
    }

	public class RpcLocal
	{
        private struct AccessedMember
        {
            public PropertyNode Node;
            public Object Owner;
        }

        /// <summary>
        /// Store all the targets with their generated or cached trees.
        /// </summary>
        public List<RpcTarget> Targets;

		// Store the client (we use this for communication)
        public RpcSenderFn Sender;

        public RpcLocal(Object target, IPropertyProvider propertyProvider, RpcSenderFn sender) :
            this(target.AsEnumerable(), propertyProvider, sender)
        {
        }

		// Constructor
        public RpcLocal(IEnumerable<Object> targetObjects, IPropertyProvider propertyProvider, RpcSenderFn sender)
		{
            // Save the rpc-sender away for later use
            this.Sender = sender;

            // Create the list of targets
            this.Targets = new List<RpcTarget>();

            // Loop through all the targets
            foreach (var targetObject in targetObjects)
            {
                // Loop through all owners and 
                var type = targetObject.GetType();

                // Create an object to hold all the information we pass into the provider interface
                var info = new PropertyProviderInfo()
                {
                    RootType = type,
                    RootObject = targetObject,
                    Rpc = this,
                    IsMemberValid = RpcMembersOnly,
                };

                // Get the property tree for the current target (only allow rpc members)
                var flattenedHierarchy = propertyProvider.GetPropertyTree(info);

                // Store the target and its generated tree
                this.Targets.Add(new RpcTarget() { FlattenedHierarchy = flattenedHierarchy, TargetObject = targetObject });
            }
		}

        private UInt32 GetMemberIndex(Object[] members)
        {
            return 0;
        }

        private static Boolean RpcMembersOnly(MemberInfo member)
        {
            // Check if they have the rpc attribute
            return member.GetCustomAttributes(typeof(RpcAttribute), false).Length != 0;
        }

		/// <summary>
        /// Handle received rpc data (by this point we know it belongs to rpc).
		/// </summary>
		/// <param name="data">The data we received.</param>
		public void Receive(Byte[] data)
		{
            // Make a binary reader to read the data
            var reader = data.ToBinaryReader();

            // Read in the safe guard marker
            var safeGuard = (SafeGuardMark)reader.ReadByte();

            // Read the tree id so we know if this packet is old or not
            var treeId = reader.ReadUInt32();

            // Read the hashes of the members (so we know who we're accessing)
            var memberIndex = reader.ReadUInt32();

            // Loop through all the targets
            foreach (var target in this.Targets)
            {
                // Get the corresponding member that was accessed for this tree
                // Note that this access MUST be valid for all trees on all targets
                var access = this.GetMember(memberIndex, target);
                //var access = this.TraverseTree(memberHashes, 0, target.TargetObject, target.Root);

                // If the node is a method node (we have a method associated with it)
                if (access.Node.Method != null)
                {
                    // Error checking
                    if (safeGuard != SafeGuardMark.Call)
                    {
                        throw new Exception("An attempted Rpc Set was made to a method");
                    }

                    // Read the number of parameters to the method call
                    var numParameters = reader.ReadByte();

                    // Create an array of objects to store the parameters (so we can invoke the method)
                    var parameters = new Object[numParameters];

                    // Read all the parameters
                    for (int i = 0; i < numParameters; ++i)
                    {
                        // Read the current parameter
                        parameters[i] = reader.ReadDynamic();
                    }

                    // Invoke the method with the given parameters
                    access.Node.Method.Invoke(access.Owner, parameters);
                }
                // Otherwise, we just assume that it's either a value or object
                else
                {
                    // Error checking
                    if (safeGuard != SafeGuardMark.Set)
                    {
                        throw new Exception("An attempted Rpc Call was made to a field or property member");
                    }

                    // Read the value that we want to use
                    var value = reader.ReadDynamic();

                    // Set the value to a value that we dynamically read/create from the stream
                    access.Node.Setter(access.Owner, value);

                    // Also set the value on the node
                    access.Node.CurrentValue = value;

                    // We may want to do this in the future
                    // (it should do both the above lines, however the only reason we don't is because there would be other errors before this even works)
                    //access.Node.SetValue(access.Owner, value);
                }
            }
		}

        /// <summary>
        /// Get a member of the given member index from the rpc target.
        /// </summary>
        /// <param name="memberIndex">The member index that the member should exist at.</param>
        /// <param name="target">The target contains the root object and flattened heirarchy of nodes.</param>
        /// <returns>Returns the owner and the associated node.</returns>
        private AccessedMember GetMember(UInt32 memberIndex, RpcTarget target)
        {
            // Get the node associated with the member index from the flattened heirarchy
            var node = target.FlattenedHierarchy[memberIndex];

            // Use a recursive call to walk up the parents, then walk back down and get each object
            // along the way (the final one would result in the owner of the current node)
            var owner = this.GetMemberOwnerRecursive(node, target.TargetObject);

            // Returned the 'accessed member' which tells us which node and relative owner was involved
            return new AccessedMember()
            {
                Node = node,
                Owner = owner,
            };
        }

        /// <summary>
        /// A recursive function that we call to get the owner of a given node. Note that this call
        /// must walk up the heirarchy to the root, then use the root object to walk back down getting
        /// each sub owner as we go, until we reach the owner who matches the node.
        /// </summary>
        /// <param name="node">The node who we'd like to traverse all owners, starting from the root.</param>
        /// <param name="root">The root object in the tree.</param>
        /// <returns>The owner for the given node.</returns>
        private Object GetMemberOwnerRecursive(PropertyNode node, Object root)
        {
            // If we have no parent (base case)
            if (node.Parent == null)
            {
                // Then return the root object
                return root;
            }
            else
            {
                // Otherwise, traverse up then back down getting each owner as we go
                return node.Getter(this.GetMemberOwnerRecursive(node.Parent, root));
            }
        }
	}

    public class RpcRemote
    {
        private struct AccessedMember
        {
            public PropertyNode Node;
            public Object Owner;
        }

        /// <summary>
        /// Store all the targets with their generated or cached trees.
        /// </summary>
        public List<PropertyNode[]> FlattenedHierarchies;

        // Store the client (we use this for communication)
        public RpcSenderFn Sender;

        // Constructor
        public RpcRemote(RpcSenderFn sender)
        {
            // Save the rpc-sender away for later use
            this.Sender = sender;
        }

        private UInt32 GetMemberIndex(Object[] members)
        {
            return 0;
        }

        // Set the value of a remote variable
        private static Byte[] GenerateSet(UInt32 member, Object value)
        {
            // Create a memory stream to write data to
            MemoryStream stream = new MemoryStream();

            // Create a binary writer to write the data
            BinaryWriter writer = new BinaryWriter(stream);

            // Write a safe guard mark for debugging and error checking
            writer.Write((Byte)MessageType.Set);

            // Write the member data to the stream
            writer.Write(member);

            // Write the value so that the other side knows what to set
            writer.WriteDynamic(value);

            // Send the rpc data
            return stream.ToArray();
        }

        // Call a remote method
        private static Byte[] GenerateCall(UInt32 member, params Object[] parameters)
        {
            // Create a memory stream to write data to
            MemoryStream stream = new MemoryStream();

            // Create a binary writer to write the data
            BinaryWriter writer = new BinaryWriter(stream);

            // Write a safe guard mark for debugging and error checking
            writer.Write((Byte)MessageType.Call);

            // Write the member data to the stream
            writer.Write(member);

            // Write the number of parameters
            writer.Write((Byte)parameters.Length);

            // Loop through all the parameters and write them out
            for (int i = 0; i < parameters.Length; ++i)
            {
                // Write the parameter value
                writer.WriteDynamic(parameters[i]);
            }

            // Send the rpc data
            return stream.ToArray();
        }

        // Set the value of a remote variable
        public void Set(UInt32 member, Object value)
        {
            this.Sender(GenerateSet(member, value));
        }

        // Set the value of a remote variable
        public void Set(Object[] members, Object value)
        {
            this.Set(GetMemberIndex(members), value);
        }

        // Call a remote method
        public void Call(UInt32 member, params Object[] parameters)
        {
            this.Sender(GenerateCall(member, parameters));
        }

        // Call a remote method
        public void Call(Object[] members, params Object[] parameters)
        {
            this.Call(GetMemberIndex(members), parameters);
        }


        /// <summary>
        /// Handle received rpc data (by this point we know it belongs to rpc).
        /// </summary>
        /// <param name="data">The data we received.</param>
        public void Receive(Byte[] data)
        {
            // Make a binary reader to read the data
            var reader = data.ToBinaryReader();

            // Read in the message type
            var messageType = (MessageType)reader.ReadByte();

            // Based on the message type...
            switch (messageType)
            {
                // On the remote side, we can get a message that communicates the entire property tree to us
                case MessageType.Trees:
                {
                    // Read the number of trees that we're being sent (typically just one)
                    var treeCount = reader.ReadInt32();

                    this.Roots = new List<PropertyNode>();

                    for (Int32 i = 0; i < treeCount; ++i)
                    {
                        this.Roots.Add(ReadTree(reader, this.PropertyRpc, 0, null));
                    }

                    this.Form.PropGrid.SelectedObjects = this.Roots.ToArray<Object>();
                    break;
                }

                case MessageType.PropertySnapshot:
                {
                    foreach (var root in this.Roots)
                    {
                        // Read all the properties for each tree
                        ReadProperties(reader, null, root);
                    }
                    break;
                }

                default:
                    throw new Exception("Unhandled message type!");
            }
        }

        /// <summary>
        /// Get a member of the given member index from the rpc target.
        /// </summary>
        /// <param name="memberIndex">The member index that the member should exist at.</param>
        /// <param name="target">The target contains the root object and flattened heirarchy of nodes.</param>
        /// <returns>Returns the owner and the associated node.</returns>
        private AccessedMember GetMember(UInt32 memberIndex, RpcTarget target)
        {
            // Get the node associated with the member index from the flattened heirarchy
            var node = target.FlattenedHierarchy[memberIndex];

            // Use a recursive call to walk up the parents, then walk back down and get each object
            // along the way (the final one would result in the owner of the current node)
            var owner = this.GetMemberOwnerRecursive(node, target.TargetObject);

            // Returned the 'accessed member' which tells us which node and relative owner was involved
            return new AccessedMember()
            {
                Node = node,
                Owner = owner,
            };
        }

        /// <summary>
        /// A recursive function that we call to get the owner of a given node. Note that this call
        /// must walk up the heirarchy to the root, then use the root object to walk back down getting
        /// each sub owner as we go, until we reach the owner who matches the node.
        /// </summary>
        /// <param name="node">The node who we'd like to traverse all owners, starting from the root.</param>
        /// <param name="root">The root object in the tree.</param>
        /// <returns>The owner for the given node.</returns>
        private Object GetMemberOwnerRecursive(PropertyNode node, Object root)
        {
            // If we have no parent (base case)
            if (node.Parent == null)
            {
                // Then return the root object
                return root;
            }
            else
            {
                // Otherwise, traverse up then back down getting each owner as we go
                return node.Getter(this.GetMemberOwnerRecursive(node.Parent, root));
            }
        }
    }

	// An attribute that we attach to tell us what is available to Rpc
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field)]
	public class RpcAttribute : Attribute
	{
	}
}
